package com.apexcode.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.apexcode.common.core.constants.Constants;
import com.apexcode.common.core.enums.ResultCode;
import com.apexcode.common.security.exception.ServiceException;
import com.apexcode.system.domain.exam.Exam;
import com.apexcode.system.domain.exam.ExamQuestion;
import com.apexcode.system.domain.exam.dto.ExamAddDTO;
import com.apexcode.system.domain.exam.dto.ExamEditDTO;
import com.apexcode.system.domain.exam.dto.ExamQueryDTO;
import com.apexcode.system.domain.exam.dto.ExamQuestionAddDTO;
import com.apexcode.system.domain.exam.vo.ExamDetailVO;
import com.apexcode.system.domain.exam.vo.ExamVO;
import com.apexcode.system.domain.question.Question;
import com.apexcode.system.domain.question.vo.QuestionVO;
import com.apexcode.system.manager.ExamCacheManager;
import com.apexcode.system.mapper.exam.ExamMapper;
import com.apexcode.system.mapper.exam.ExamQuestionMapper;
import com.apexcode.system.mapper.question.QuestionMapper;
import com.apexcode.system.service.exam.IExamService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @ClassName ExamServiceImpl
 * @Description
 * @Author ZJX
 * @Date 2025/1/29 17:01
 * @Version 1.0
 **/
@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion> implements IExamService {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private ExamCacheManager examCacheManager;


    @Override
    public List<ExamVO> list(ExamQueryDTO examQueryDTO) {
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        return examMapper.selectExamList(examQueryDTO);
    }

    @Override
    public int add(ExamAddDTO examAddDTO) {
        List<Exam> examList = examMapper
                .selectList(new LambdaQueryWrapper<Exam>().eq(Exam::getTitle, examAddDTO.getTitle()));

        validateExamRequest(examAddDTO, examList);

        Exam exam = new Exam();
        BeanUtil.copyProperties(examAddDTO, exam);
        return examMapper.insert(exam);
    }

    @Override
    public boolean questionAdd(ExamQuestionAddDTO examQuestionAddDTO) {
        Exam exam = examMapper.selectById(examQuestionAddDTO.getExamId());
        validateExamStartTime(exam);
        if (Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        Set<Long> questionIdSet = examQuestionAddDTO.getQuestionIdSet();
        if (CollectionUtil.isEmpty(questionIdSet)) {
            return true;
        }

        List<Question> questionList = questionMapper.selectBatchIds(questionIdSet);
        if (CollectionUtil.isEmpty(questionList) || questionList.size() < questionIdSet.size()) {
            throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }
        return saveExamQuestion(exam, questionIdSet);
    }

    @Override
    public int questionDelete(Long examId, Long questionId) {
        Exam exam = getExam(examId);
        validateExamStartTime(exam);
        if (Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        return examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
    }

    @Override
    public ExamDetailVO detail(Long examId) {
        Exam exam = getExam(examId);
        ExamDetailVO examDetailVO = new ExamDetailVO();
        BeanUtil.copyProperties(exam,examDetailVO);

        List<QuestionVO> questionVOList = examQuestionMapper.selectExamQuestionList(examId);
        if (CollectionUtil.isEmpty(questionVOList)){
            return examDetailVO;
        }

        examDetailVO.setExamQuestionList(questionVOList);
        return examDetailVO;
    }

    @Override
    public int edit(ExamEditDTO examEditDTO) {
        Exam exam = getExam(examEditDTO.getExamId());
        if (Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        validateExamStartTime(exam);
        checkExamSaveParams(examEditDTO, examEditDTO.getExamId());
        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());
        return examMapper.updateById(exam);
    }

    @Override
    public int delete(Long examId) {
        Exam exam = getExam(examId);
        if (Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        validateExamStartTime(exam);
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        return examMapper.deleteById(exam);
    }

    @Override
    public int publish(Long examId) {
        Exam exam = getExam(examId);

        //select count(0) from tb_exam_question where exam_id = #{examId}
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }

        Long count = examQuestionMapper
                .selectCount(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        if (count == null || count <= 0) {
            throw new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
        }
        exam.setStatus(Constants.TRUE);

        //要将新发布的竞赛数据存储到redis   e:t:l  e:d:examId
        examCacheManager.addCache(exam);
        return examMapper.updateById(exam);
    }

    @Override
    public int cancelPublish(Long examId) {
        Exam exam = getExam(examId);
        validateExamStartTime(exam);
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }
        exam.setStatus(Constants.FALSE);
        examCacheManager.deleteCache(examId);
        return examMapper.updateById(exam);
    }


    /**
     * 检查竞赛是否已经开始。
     * 如果竞赛开始时间在当前时间之前，抛出异常。
     *
     * @param exam 需要检查的竞赛对象
     * @throws ServiceException 如果竞赛已开始
     */
    private void validateExamStartTime(Exam exam) {
        if (exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }
    }

    /**
     * 检查保存竞赛的参数是否有效。
     * 1. 判断竞赛标题是否重复
     * 2. 检查竞赛的开始和结束时间是否有效
     *
     * @param examSaveDTO 需要保存的竞赛数据传输对象
     * @param examId 要更新的竞赛ID
     * @throws ServiceException 如果标题重复或时间无效
     */
    private void checkExamSaveParams(ExamAddDTO examSaveDTO, Long examId) {
        //1、竞赛标题是否重复进行判断   2、竞赛开始、结束时间进行判断
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, examSaveDTO.getTitle())
                .ne(examId != null, Exam::getExamId, examId));

        validateExamRequest(examSaveDTO, examList);
    }

    /**
     * 校验竞赛保存请求的有效性。
     * 检查标题是否重复，开始时间是否晚于当前时间，开始时间是否在结束时间之前。
     *
     * @param examSaveDTO 需要保存的竞赛数据传输对象
     * @param examList 查询到的已有竞赛列表（用来检查标题是否重复）
     * @throws ServiceException 如果标题重复、开始时间无效或时间不合理
     */
    private void validateExamRequest(ExamAddDTO examSaveDTO, List<Exam> examList) {
        if (CollectionUtil.isNotEmpty(examList)) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }

        if (examSaveDTO.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);  //竞赛开始时间不能早于当前时间
        }

        if (examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
        }
    }

    /**
     * 保存竞赛题目与竞赛的关联关系。
     * 生成竞赛问题对象，并批量保存。
     *
     * @param exam 竞赛对象
     * @param questionIdSet 需要关联的题目ID集合
     * @return 是否保存成功
     */
    private boolean saveExamQuestion(Exam exam, Set<Long> questionIdSet) {
        int num = 1;
        List<ExamQuestion> examQuestionList = new ArrayList<>();
        for (Long questionId : questionIdSet) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(num++);
            examQuestionList.add(examQuestion);
        }
        return saveBatch(examQuestionList);
    }

    /**
     * 获取指定ID的竞赛对象。
     * 如果竞赛不存在，则抛出异常。
     *
     * @param examId 需要获取的竞赛ID
     * @return Exam 如果竞赛存在，返回竞赛对象
     * @throws ServiceException 如果竞赛不存在
     */
    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam == null) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        return exam;
    }

}
